iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0

Part 2|第 6/30 篇
今日要做的事: 把餐點照接到「真的能寫進 today_meals」——結構壞了怎麼救、語意不穩怎麼問人。
今天要解決的目的: Part 2 收工。契約定了,明天才上 Firebase。

Day 4 格式漂亮,它卻不說「不確定」。
今天我本來想找一張「完全寫不出來」的翻車照……結果翻車沒翻成功,反而逼我把進庫的門講清楚。


今日任務卡

項目 內容
產出 進庫管線(recovery → Verifier → 寫入)、確認卡規則、一筆壞照實跑
工具 AI Studio(Gemini 3.1 Pro Preview);本地一小段 validator
不使用 Firebase(明天才上)、confidence、無限 retry、第二個 LLM 修 JSON
素材 布丁波羅麵包:標籤清楚照(正解)+刻意拍糊的測試照

1. 問題:進得去,不代表我敢存

Day 4 的感覺很尷尬:JSON 解得開,我卻不敢存。parsed_ok=1,該確認的時候它還跟我說不用確認。

若這時候直接上 Firestore,只會多一批「長得很合法的錯資料」。所以 Part 2 收尾就做一件事——門怎麼開

https://ithelp.ithome.com.tw/upload/images/20260920/20121052dp4dXOOKn5.jpg

圖:照片 → SO → 結構閘門 → 語意閘門 → 才寫 today_meals

① 管形狀,② 管敢不敢信。兩件事不要混在同一個 retry 裡——語意看錯再問一次,常常只換到一份更流暢的錯答案。


2. 設計:兩道門

2.1 結構:只救一次

常見三種:parse_errormissing_requiredinvalid_enum

不做
L1 帶 error 重送一次 再送第二次
L2 外食→eat_out;未知 tag 進 extra_tags 猜主菜、塞空 items
L3 人選粗欄位,raw 留著 叫人重貼整段 Prompt
async function recover(request, firstRaw) {
  const a0 = inspect("L0", firstRaw);
  if (a0.ok) return pass([a0]);

  const a1 = inspect("L1", await runModelOnce(request, a0.errors));
  if (a1.ok) return pass([a0, a1]);

  const mapped = normalizeDeterministically(a1);
  if (mapped.ok) return pass([a0, a1], "L2", mapped.value);

  return manualFallback([a0, a1]); // L3
}

L2 不再呼叫模型。缺 itemslabel 不能裝成功。

2.2 語意:模型不能自己蓋章

needs_confirmation 不進模型 schema,由程式依 decision 推:

const needs_confirmation = decision === "CONFIRM";
const write_allowed = decision === "ACCEPT";

https://ithelp.ithome.com.tw/upload/images/20260920/20121052asiMndOZDI.jpg

圖:ACCEPT 可寫;CONFIRM 最多兩題;REJECT 重拍或手動。

就算模型回了 uncertain_items,人標的 capture_flags(blur/glare/裁切)仍要一起算——補 Day 4「它自己說很確定」的洞。問題要能改欄位;人答完另存 human_patch,不改 model_raw


3. 實跑:想翻車,結果翻車沒翻成功

說實在的,我今天原本想找一個「完整翻車」——最好它吐出一坨解不開的字、或亂編一道我從沒看過的菜,我才好示範 REJECT、L1 retry、整條 recovery。

於是我去櫃上先拍清楚正解。標籤寫 布丁波羅麵包,35 元:

https://ithelp.ithome.com.tw/upload/images/20260920/20121052lSZv2u55Ej.png

圖:人眼 Ground Truth。價簽寫死品名。

接著故意搞破壞:失焦、裁到只剩一角黃心、塑膠袋反光拉滿。心裡想的是:「這總該寫不出來了吧。」

https://ithelp.ithome.com.tw/upload/images/20260920/20121052opLUv98Ph5.jpg

圖:丟進模型的測試輸入。人先記 capture_flags: ["blur"](裁切明顯也可加 occluded)。

結果——翻車沒翻成功。

AI Studio 一樣 Temp 0、Structured outputs 、Tools 全關。它不但吐出合法 JSON,還很客氣地承認自己看不清口味:

https://ithelp.ithome.com.tw/upload/images/20260920/20121052CDYQWTvVjS.jpg

圖:SO 開、Temperature 0。格式過了,沒有解不出來的慘案。

{
  "slot": "snack",
  "label": "麵包",
  "food_source": "convenience_store",
  "items": [
    { "name": "麵包", "role": "staple", "cook_method": "unknown" }
  ],
  "tags": ["refined_carb_heavy"],
  "uncertain_items": ["麵包口味"]
}

當下我有點哭笑不得:我準備好的是「寫不出來」的劇本,它給我的是「寫得出來、但不敢寫死」的劇本。品名沒對到布丁波羅,可是比 Day 4 那輪空的 uncertain_items 誠實多了。

檢查 結果
我以為會怎樣 JSON 掛掉/亂編品名 → 好示範 REJECT
實際怎樣 結構過;只寫「麵包」;口味進 uncertain
跟正解比 粗,但沒裝成「確定是某某口味」
Verifier CONFIRM(uncertain 非空+人標 blur)→ 不准直接寫入

這種「想翻車卻沒翻成」其實更接近真實使用:模型常常不是炸裂,而是溫柔地裝有把握或溫柔地含糊。所以門不能只防崩潰,還要防「看起來能存」。

正式進庫前仍要問人,例如:

{
  "decision": "CONFIRM",
  "needs_confirmation": true,
  "questions_for_user": [
    { "field": "items[0].name", "options": ["布丁波羅麵包", "一般麵包", "看不出來"] }
  ],
  "write_allowed": false
}

我選「布丁波羅麵包」,另存 human_patch,再跑同一套 Verifier;變 ACCEPT 才准寫。food_source 它寫成 convenience_store,若其實是麵包店,同一輪確認一起改就好——重點是 model_raw 原封不動留著,之後才分得出來誰改過什麼。

語意閘門骨架(本機可測,還沒接雲):

function verifyMeal(modelRaw, captureFlags) {
  const parsed = safeParseAndValidate(modelRaw);
  if (!parsed.ok) return result("REJECT", modelRaw, ["invalid_structure"]);
  if (captureFlags.some(x => ["not_food", "unreadable"].includes(x)))
    return result("REJECT", modelRaw, captureFlags);

  const reasons = [
    ...parsed.value.uncertain_items.map(x => x.reason ?? x),
    ...captureFlags.filter(x => ["glare", "blur", "occluded", "too_far"].includes(x)),
    ...findContradictions(parsed.value)
  ];
  const questions = buildFieldQuestions(parsed.value, reasons).slice(0, 2);
  if (reasons.length && !questions.length) return result("REJECT", modelRaw, reasons);
  if (reasons.length) return result("CONFIRM", modelRaw, reasons, questions);
  return result("ACCEPT", modelRaw, []);
}

結構那邊的 L1/L2/L3,今天沒等到「截斷 JSON」那種真實炸裂;附錄 B 先用 fixture 保底。反正規則寫好,真遇到再套。


4. 翻車點(含「翻車失敗」這種翻車)

容易怎樣 怎麼守
以為一定能拍到模型崩潰 崩潰不是常態;含糊+合法 JSON 才常見
有 uncertain 仍當 ACCEPT Verifier 看到非空就 CONFIRM
只信模型、忽略糊圖 capture_flags 人寫,模型蓋不掉
L2 把缺 items 補成 [] 核心欄缺 → L3
一直 retry L1 上限 1;語意錯不進結構 retry

5. 今日結論+明天預告

  1. 進庫=結構+語意兩道門;只有 ACCEPT 才寫。
  2. 我想示範大翻車,結果它只肯含糊。 布丁波羅 →「麵包+口味不確定」→ CONFIRM,用人補正——這比較像日常。
  3. Part 2 收工。 Meal 契約、進庫條件都定了,再繼續在 AI Studio 裡玩辨識,只會原地打轉。

下一篇開始上 Firebase。 不是為了「有用雲端」,是資料要活過隔天:誰的 uid、today_meals/pantry 長什麼樣、什麼條件才准寫入(就是今天的 decision === "ACCEPT")。先 Auth 把「這是誰的」釘住,後面 Rules、Storage 才有意義。

下一篇: Firebase Auth——資料要活過隔天,先回答「這是誰的」。


附錄 A:模型草稿 schema(不含 needs_confirmation)

{
  "type": "object",
  "properties": {
    "slot": { "type": "string", "enum": ["breakfast", "lunch", "dinner", "snack"] },
    "label": { "type": "string" },
    "food_source": {
      "type": "string",
      "enum": ["home", "convenience_store", "supermarket", "eat_out"]
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "role": { "type": "string", "enum": ["staple", "main", "side", "other"] },
          "cook_method": {
            "type": "string",
            "enum": ["fried", "braised", "stir_fried", "steamed", "grilled", "raw", "unknown"]
          }
        },
        "required": ["name", "role", "cook_method"]
      }
    },
    "tags": {
      "type": "array",
      "maxItems": 4,
      "items": {
        "type": "string",
        "enum": [
          "fried", "braised", "stir_fried", "poultry", "seafood",
          "protein_present", "vegetable_present", "vegetable_low",
          "sauce_heavy", "refined_carb_heavy"
        ]
      }
    },
    "uncertain_items": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["slot", "label", "food_source", "items", "tags", "uncertain_items"]
}

附錄 B:結構 recovery 備註

  • Alias:外食→eat_out烤→grilled滷→braised
  • Fixture:截斷 JSON→L1;food_source:"外食"→L2;缺 items→L3。
  • 每次 raw 不可被 normalize 覆寫。

上一篇
[Day 4] 餐點照終於進了 JSON,但模型突然不會說「不確定」
系列文
DishFlow AI Agent:用 Google AI 打造 Eat-Cost Balance 的下一餐決策系統6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言